package com.rdtravel.services;

import com.rdtravel.entities.DepartureLocation;
import com.rdtravel.entities.Destination;
import com.rdtravel.entities.Partner;
import com.rdtravel.entities.Trip;
import com.rdtravel.exceptions.TripsToImportInvalidException;
import com.rdtravel.valueobjects.Period;

import java.util.ArrayList;
import java.util.List;

import javax.persistence.TypedQuery;
import javax.validation.ConstraintViolation;

import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional
class JPATripsService extends JPAService implements TripsService {
	private final DestinationsService destinationsService;
	private final DepartureLocationsService departureLocationsService;
	private final PartnersService partnersService;

	@Autowired
	public JPATripsService(DestinationsService destinationsService,
			DepartureLocationsService departureLocationsService,
			PartnersService partnersService) {
		super();
		this.destinationsService = destinationsService;
		this.departureLocationsService = departureLocationsService;
		this.partnersService = partnersService;
	}

	@Override
	public List<Trip> findByDestinationAndDepartureLocationAndPeriod(
			Destination destination, DepartureLocation departureLocation,
			Period period, int seats) {
		TypedQuery<Trip> query = entityManager.createNamedQuery(
				"findTripsByDestinationAndPeriodeAndDepartureLocation",
				Trip.class);
		query.setParameter("destination", destination);
		query.setParameter("departureLocation", departureLocation);
		query.setParameter("departureDate", period.getDepartureDate());
		query.setParameter("returnDate", period.getReturnDate());
		query.setParameter("seats", seats);
		return query.getResultList();
	}

	@Override
	public Trip findById(long id) {
		return entityManager.find(Trip.class, id);
	}

	@Override
	public List<Trip> findFutureTrips() {
		TypedQuery<Trip> query = entityManager.createNamedQuery(
				"findFutureTrips", Trip.class);
		return query.getResultList();
	}

	@Override
	public void importTrips(List<Trip> trips) {
		List<String> errors = new ArrayList<String>();
		for (Trip trip : trips) {
			DirectFieldAccessor directFieldAccessor = new DirectFieldAccessor(
					trip);
			directFieldAccessor.setPropertyValue("id", 0);
			int numberOfErrorsBeforeValidating = errors.size();
			processImportedDestionation(trip, errors);
			processImportedDepartureLocation(trip, errors);
			processImportedPartner(trip, errors);
			for (ConstraintViolation<Trip> constraintViolation : validator
					.validate(trip)) {
				errors.add(constraintViolation.getPropertyPath().toString()
						+ " value " + constraintViolation.getInvalidValue()
						+ ' ' + constraintViolation.getMessage());
			}
			if (errors.size() == numberOfErrorsBeforeValidating) {
				try {
					entityManager.persist(trip);
				} catch (Throwable ex) {
					errors.add(ex.getMessage());
				}
			}
		}
		if (!errors.isEmpty()) {
			throw new TripsToImportInvalidException(errors);
		}
	}

	private void processImportedPartner(Trip trip, List<String> errors) {
		String name = trip.getPartner().getName();
		Partner partner = partnersService.findByName(name);
		if (partner == null) {
			errors.add("Partner not found:" + name);
		} else {
			trip.setPartner(partner);
		}
	}

	private void processImportedDepartureLocation(Trip trip, List<String> errors) {
		String name = trip.getDepartureLocation().getName();
		DepartureLocation departureLocation = departureLocationsService
				.findByName(name);
		if (departureLocation == null) {
			errors.add("Departure location not found:" + name);
		} else {
			trip.setDepartureLocation(departureLocation);
		}
	}

	private void processImportedDestionation(Trip trip, List<String> errors) {
		String name = trip.getDestination().getName();
		Destination destination = destinationsService.findByName(name);
		if (destination == null) {
			errors.add("Destination not found:" + name);
		} else {
			trip.setDestination(destination);
		}
	}

}
